/*
 * 
 */

package multiplieralgorithm;

/**
 *
 * @author Kylan Robinson (krobinso@eecs.wsu.edu)
 */
public class Multiplier{

    int dimension;
    int m;
    Cell[][] cellfield;

    public Multiplier() {
        dimension = 1;
        m = 1;
        cellfield = new Cell[dimension][dimension];
        //iterate through the rows
        for(int i = 0; i < dimension; i++) {
            //iterate through the columns
            for(int j = 0; j < dimension; j++) {
                cellfield[i][j] = new Cell();
            }
        }
    }
    
    //n is the number of bits overall
    //m is the number of bits per cell
    public Multiplier(int new_n, int new_m) {
        dimension = new_n/new_m;
        m = new_m;
        
        //create the field of cells
        cellfield = new Cell[dimension][dimension];
        //initialize the cell field
        for(int i = 0; i < dimension; i++) {
            for(int j = 0; j < dimension; j++) {
                cellfield[i][j] = new Cell();
            }
        }
    }
    
    //n is the number of bits overall
    //m is the number of bits per cell
    //mtype is the type of multiplier
    public Multiplier(int new_n, int new_m, int mtype) {
        int i,j;
        
        dimension = new_n/new_m;
        System.out.println("DIMENSION:" + dimension);
        m = new_m;
        System.out.println("M:" + m);
        
        //create the field of cells
        cellfield = new Cell[dimension][dimension];
        
        //avoid NPE for the whole multiplier
        for(i = 0; i < dimension; i++) {
            for(j = 0; j < dimension; j++) {
                cellfield[i][j] = new Cell();
            }
        }

        //set up as a two's complement multiplier
        if(mtype == 1) { 
            //set the cell types
            //configure the first row  
            //upper left cell is of type B
            cellfield[0][0].setType('B', m);
            //the rest of the row is of type A
            for(i = 1; i < dimension; i++) {
                cellfield[0][i].setType('A', m);
            }
            
            //configure the inner rows
            //iterate through the innerrows
            for(i = 1; i < dimension - 1; i++) {     
                cellfield[i][0].setType('D', m);
                //iterate over the row entries
                for(j = 1; j < dimension; j++) {   
                    if(j == i) {
                        //diagonal positions are of type C
                        cellfield[i][j].setType('C', m);   
                    }
                    else {
                        //all others are of type A
                        cellfield[i][j].setType('A', m);   
                    }
                }
            }
            
            //configure the last row
            //the lower left corner is of type H
            cellfield[dimension - 1][0].setType('H', m);
            //the lower right corner is of type E
            cellfield[dimension - 1][dimension - 1].setType('E', m);
            //all other are of type F
            for(i = 1; i < dimension - 1; i++) {
                //set cell type
                cellfield[dimension - 1][i].setType('F', m);
            }
        }
        
        //set up as an unsigned multipler
        else if(mtype == 0) {
            //iterate over the rows
            for(i = 0; i < dimension; i++) {
                //iterate over the columns
                for(j = 0; j < dimension; j++) {
                    //set the cell type
                    cellfield[i][j].setType('A', m);
                }
            }
        }
        
        //fall-through case
        else {
            //error, type not recognized
            //set the cell types
            //iterate over the rows
            for(i = 0; i < dimension; i++) {
                //iterate over the columns
                for(j = 0; j < dimension; j++) {
                    //set the cell type (error)
                    cellfield[i][j].setType('X');
                }
           }
        }
    }
    
    //multiply function with no carry-ins
    public int multiply(int in1, int in2) {
        return multiply(in1, in2, 0, 0);
    }
    
    //multiply function with carry-in values
    public int multiply(int inA, int inB, int inC, int inD) {
        int i, j, result;
        OpArray opA, opB, opC, opD;
        
        //split the inputs into m-bit chunks
        opA = new OpArray(inA, m, dimension);
        opB = new OpArray(inB, m, dimension);
        opC = new OpArray(inC, m, dimension);
        opD = new OpArray(inD, m, dimension);
        
        //assign the chunks to their corresponding cells
        for(i = 0; i < dimension; i++) {
            for(j = 0; j < dimension; j++) {
                //set cell values
                cellfield[i][j].setOperands(opA.array[i], //start from the left
                    opB.array[dimension - 1 - j], //start from the right
                    0, 0); //no carries
            }
        }
        
        //MSBs of A and B need to be in two's complement form
        for(i = 0, j = 0; i < dimension; i++, j++) {
            cellfield[i][0].opB = tc(cellfield[i][0].opB);
            cellfield[dimension - 1][j].opA = tc(cellfield[dimension - 1][j].opA);
        }
        
        //for testing only, remove later
        printValues();
        
        //compute
        result = compute();
        
        //return the result
        return result;
    }
    
    public int compute() {
        int i = 0; //row index (initialized to top row) 
        int j = dimension - 1; //col index (initialized to right col)
        int k = 0; //final_result index
        int sum;
        OpArray intermediate, 
                final_result = new OpArray(0, m, 2*dimension);
        
        //compute each cell in a systematic manner
        //top to bottom, right to left
        for(i = 0; i < dimension; i++) {
            for(j = dimension - 1; j >= 0; j--) {
                
                //performed the computations
                sum = cellfield[i][j].compute();
                
                //split the sum into two intermediate results
                //idx 0 = LSB, idx 1 = MSB
                intermediate = new OpArray(sum, m, 2);
                
                //send the intermediate results to the next stage
                //depends on multiplier type
                
                switch(cellfield[i][j].type) {
                    case 'A': //cell type A
                        if(j == dimension - 1) { //right column
                            //LSB to final result
                            final_result.array[k++] = intermediate.array[0];
                            //MSB goes down
                            cellfield[i + 1][j].incD(intermediate.array[1]);
                        }
                        else if(j > i) { //upper right
                            //LSB goes diagonal right
                            cellfield[i + 1][j + 1].incC(intermediate.array[0]);
                            //MSB goes down
                            cellfield[i + 1][j].incD(intermediate.array[1]);
                        }
                        else { //lower left
                            //LSB goes diagonal right
                            cellfield[i + 1][j + 1].incC(intermediate.array[0]);
                            //MSB goes left
                            cellfield[i][j - 1].incD(intermediate.array[1]);
                        }
                        break;
                    case 'B': //cell type B
                        //LSB goes diagonal right
                        cellfield[i + 1][j+ 1].incC(tc(intermediate.array[0]));                        
                        //MSB goes down
                        cellfield[i + 1][j].incC(tc(intermediate.array[1]));
                        break;
                    case 'C': //cell type C
                        //LSB goes diagnoal right
                        cellfield[i + 1][j + 1].incC(tc(intermediate.array[0]));
                        //MSB goes left
                        cellfield[i][j - 1].incD(intermediate.array[1]);
                        break;
                   case 'D': //cell type D
                        //LSB goes diagonal right
                        cellfield[i + 1][j + 1].incC(intermediate.array[0]); 
                        //MSB goes down
                        cellfield[i + 1][j].incC(tc(intermediate.array[1]));
                        break;
                    case 'E': //cell type E
                        //LSB goes to final result
                        final_result.array[k++] = intermediate.array[0]; 
                        //MSB goes to the left
                        cellfield[i][j - 1].incD(tc(intermediate.array[1]));
                        break;
                    case 'F': //cell type F
                        //LSB goes to final result
                        final_result.array[k++] = intermediate.array[0];
                        //MSB goes to the left
                        cellfield[i][j - 1].incD(tc(intermediate.array[1])); 
                        break;
                    case 'G': //cell type G
                        //TODO: implement this cell type
                        break;
                    case 'H': //cell type H
                        //LSB, MSB go to final result
                        final_result.array[k++] = intermediate.array[0];
                        final_result.array[k++] = intermediate.array[1];
                        break;
                    default: //unknown cell type                    
                        break;
                }
            }
        }
        
        System.out.println("result::");
        for(i = final_result.array.length; i > 0; i--) {
            System.out.print(final_result.array[i - 1] + " ");
        }
        System.out.print('\n');
        
        //convert the array of final results into an integer
        return final_result.toInt();
    }
    
    //find the two's complement value of the operand
    public int tc(int op) {
        //if the number is negative
        if(op > Math.pow(2, m - 1) - 1) {
            return (int)(op - 2 * Math.pow(2, m - 1));
        }
        else { //the number doesn't need conversion
            return op;
        }
    }

    //print the cell types in a table
    public void printTypes() {
        //iterate through the rows
        for(int i = 0; i < dimension; i++) {
            //iterate through the columns
            for(int j = 0; j < dimension; j++) {
                //print the cell type
                cellfield[i][j].printType();
                //print one whitespace character
                System.out.print(' ');
            }
            //end of the row
            System.out.print('\n');
        }
    }
    
    //print the values held by each cell in a table
    public void printValues() {
        //iterate through the rows
        for(int i = 0; i < dimension; i++) {
            //iterate through the columns
            for(int j = 0; j < dimension; j++) {
                //print the cell operands
                cellfield[i][j].printOperands();
                //print one whitespace character
                System.out.print(' ');
            }
            //end of the row
            System.out.print('\n');
        }
    }
    
}
